01 _ 原始分布式时代:Unix设计哲学下的服务探索

你好,我是周志明。欢迎你来到“软件架构课”,从今天开始,我们就进入课程的第一个模块“演进中的架构”。

架构并不是被“发明”出来的,而是持续进化的结果。所以在这一模块中,我会借着讨论历史之名,从全局性的视角,来带你一起梳理下微服务的发展历程中,出现的大量技术名词、概念。

我会和你一起去分析,它们都是什么、取代了什么,以及为什么能够在技术发展的斗争中取得成功,为什么会成为软件架构不可或缺的支撑;又或者它们为什么会失败,为什么会逐渐被我们遗忘。

了解了这些技术的时代背景和探索过程,在后续的课程中,我再去讲解它们的原理、它们是如何解决问题的时候,你就能与它们当初的设计思想产生共鸣,能更容易深入理解其本质了。

今天这一讲,让我们先把时间拨回到半个世纪之前,一起来探讨下计算机最开始进入公众视野的时候,在Unix设计哲学的指导下,分布式架构的第一次服务化探索的得与失。

Unix的分布式设计哲学- Simplicity of both the interface and the implementation are more important than any other attributes of the system — including correctness, consistency, and completeness.- 保持接口与实现的简单性,比系统的任何其他属性,包括准确性、一致性和完整性,都来得更加重要。- —— Richard P. GabrielThe Rise of ‘Worse is Better,1991

分布式架构的目标是使用多个独立的分布式服务,来共同构建一个更大型的系统。不过,可能跟绝大多数人心中的认知有点儿差异,分布式系统的设想和它实际的尝试,反而要比你今天所了解的大型单体系统出现的时间更早。

在20世纪70年代末到80年代初,计算机科学刚经历了从以大型机为主,到向以微型机为主的蜕变,计算机也逐渐从一种存在于研究机构、实验室当中的科研设备,转变为了存在于商业企业中的生产设备,甚至是面向家庭、个人用户的娱乐设备。

这个时候的微型计算机系统,通常具有16位寻址能力、不足5MHz(兆赫)时钟频率的处理器和128KB左右的内存地址空间。比如说,著名的英特尔处理器的鼻祖,Intel 8086处理器就是在1978年研制成功,流行于80年代中期的,甚至一直到90年代初期还在生产销售。

不过,因为当时的计算机硬件的运算处理能力还相当薄弱,已经直接妨碍了单台计算机上信息系统软件能够达到的最大规模。所以,为了突破硬件算力的限制,各个高校、研究机构、软硬件厂商,都开始分头探索,想看看到底能不能使用多台计算机共同协作,来支撑同一套软件系统的运行。

这个阶段其实是对分布式架构最原始的探索与研究。你可能会觉得奇怪,计算机科学这个技术发展一日千里的领域,半个世纪之前的研究对今天还能有什么指导意义?那个时候探索的分布式如果是可行的,又怎么会拖到今时今日,软件系统才逐步进入微服务时代?

然而并非如此,从结果来看,历史局限决定了它不可能一蹴而就地解决分布式的难题,但仅从过程来看,这个阶段的探索可以称得上是硕果累累、成绩斐然。因为在这个时期提出的很多技术、概念,对Unix系统后续的发展,甚至是对今天计算机科学的很多领域,都产生了巨大而深远的影响,直接带动了后续的软件架构演化进程。

我们看一些比较熟悉的例子吧。

比如,惠普公司(及后来被惠普收购的Apollo),在80年代初期提出的网络运算架构(Network Computing Architecture,NCA),就可以说是未来远程服务调用的雏形。

再比如,卡内基 · 梅隆大学提出的AFS文件系统(Andrew File System),可以看作是分布式文件系统的最早实现(顺便一提,Andrew的意思是纪念Andrew Carnegie和Andrew Mellon)。

再比如,麻省理工学院提出的Kerberos协议,是服务认证和访问控制(ACL)的基础性协议,是分布式服务安全性的重要支撑,目前包括Windows和macOS在内的众多操作系统的登录、认证功能,等等,都会利用到这个协议。

而为了避免Unix系统的版本战争在分布式领域中重演,负责制定Unix系统技术标准的开放软件基金会(Open Software Foundation,OSF,也就是后来的“国际开放标准组织”)就邀请了各个主要的研究厂商一起参与,共同制订了“分布式运算环境”(Distributed Computing Environment,DCE)的分布式技术体系。

DCE包括了一整套完整的分布式服务组件的规范与实现。

比如,源自NCA的远程服务调用规范(Remote Procedure Call,RPC,在当时被称为是DCE/RPC),跟后来不局限于Unix系统的、基于通用TCP/IP协议的远程服务标准ONC RPC,一起被认为是现代RPC的共同鼻祖(这是Sun公司向互联网工程任务组提交的);源自AFS的分布式文件系统(Distributed File System,DFS)规范,在当时被称为DCE/DFS;源自Kerberos的服务认证规范;还有时间服务、命名与目录服务,就连现在程序中很常用的通用唯一识别符UUID,也是在DCE中发明出来的。

因为OSF本身的背景(它是一个由Unix开发者组成的Unix标准化组织),所以在当时研究这些分布式技术,通常都会有一个预设的重要原则,也就是在实现分布式环境中的服务调用、资源访问、数据存储等操作的时候,要尽可能地透明化、简单化,让开发人员不用去过于关注他们访问的方法,或者是要知道其他资源是位于本地还是远程。

这样的主旨呢,确实非常符合Unix设计哲学(有过几个版本的不同说法,这里我指的是Common Lisp作者Richard P. Gabriel提出的简单优先“Worse is Better”原则),但这个目标其实是过于理想化了,它存在一些在当时根本不可能完美解决的技术困难。

“调用远程方法”与“调用本地方法”尽管只是两字之差,但要是想能同时兼顾到简单、透明、性能、正确、鲁棒(Robust)、一致的目标的话,两者的复杂度就完全不能相提并论了。

我们先不说,远程方法是不可能做到像本地方法一样,能用内联等传统编译原理中的优化算法,来提升程序运行速度的,光是“远程”二字带来的网络环境下的新问题。

比如说,远程的服务在哪里(服务发现)、有多少个(负载均衡)、网络出现分区、超时或者服务出错了怎么办(熔断、隔离、降级)、方法的参数与返回结果如何表示(序列化协议)、如何传输(传输协议)、服务权限如何管理(认证、授权)、如何保证通信安全(网络安全层)、如何令调用不同机器的服务能返回相同的结果(分布式数据一致性)等一系列问题,就需要设计者耗费大量的心思。

那么,面对重重的困难与压力,DCE不仅从零开始、从无到有地回答了其中大部分问题,构建出了大量的分布式基础组件与协议,而且它还真的尽力去做到了相对意义的“透明”。

比如说,你在DFS上访问文件,如果不考虑性能上的差异的话,就很难感受到,它与本地磁盘文件系统有什么不同。可是,一旦考虑性能上的差异,分布式和本地的鸿沟是无比深刻的,这是数量级上的差距,是不可调和的。

尤其是在那个年代,在机器硬件的限制下,开发者为了让程序在运行效率上可以接受,就只有在方法本身的运行时间很长,可以相对忽略远程调用成本时的情况下,才去考虑使用分布式。如果方法本身的运行时长不够,就要人为地用各种奇技淫巧来刻意构造出这样的场景,比如可能会将几个原本毫无关系的方法打包到一个方法内,一块进行远程调用。

一方面,刻意构造长时间运行的方法这本身就与使用分布式来突破硬件算力、提升性能的初衷相互矛盾,需要我们小心平衡;另一方面,此时的开发人员,实际上仍然必须无时无刻地都要意识到,自己是在编写分布式的程序,不能随随便便地踏过本地与远程的界限,让软件系统的设计向性能做出妥协,让DCE“尽量简单透明”的努力几乎全部付诸东流。

因为本地与远程,无论是从编码、部署,还是从运行效率的角度上看,都有着天壤之别,所以在设计一个能运作良好的分布式应用的时候,就变得需要极高的编程技巧和各方面的知识来作为支撑,这个时候,反而是人员本身对软件规模的约束,超过机器算力上的约束了。

对DCE的研究呢,算得上是计算机科学中第一次有组织领导、有标准可循、有巨大投入的分布式计算的尝试。但无论是DCE,还是稍后出现的CORBA(Common ObjectRequest Broker Architecture,公共对象请求代理体系结构),我们从结果来看,都不能说它们取得了成功。

因为把一个系统直接拆分到不同的机器之中,这样做带来的服务的发现、跟踪、通讯、容错、隔离、配置、传输、数据一致性和编码复杂度等方面的问题,所付出的代价远远超过了分布式所取得的收益。

而亲身经历过那个年代的计算机科学家、IBM院士凯尔 · 布朗(Kyle Brown),在事后曾经评价道,“这次尝试最大的收获就是对RPC、DFS等概念的开创,以及得到了一个价值千金的教训:某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作,只会自寻苦果”。

原始分布式时代的教训- Just because something can be distributed doesn’t mean it should be distributed. Trying to make a distributed call act like a local call always ends in tears.- 某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作,只会自寻苦果。- —— Kyle Brown,IBM Fellow,Beyond buzzwords: A brief history of microservices patterns,2016

其实,从设计角度来看,以上的结论是有违Unix哲学的,但这也是在当时的现实情况下,不得不做出的让步。在当时计算机科学面前,有两条通往更大规模软件系统的道路,一条路是尽快提升单机的处理能力,以避免分布式的种种问题;另一条路是找到更完美的解决方案,来应对如何构筑分布式系统的问题。

在20世纪80年代,正是摩尔定律开始稳定发挥作用的黄金时期,微型计算机的性能以每两年就增长一倍的惊人速度在提升,硬件算力束缚软件规模的链条,很快就松动了,我们用单台或者几台计算机,就可以作为服务器来支撑大型信息系统的运作了,信息系统进入了单体时代,而且在未来很长的一段时间内,单体系统都将是软件架构的主流。

不过尽管如此,对于另外一条路径,也就是对分布式计算、远程服务调用的探索,开发者们也从没有中断过。关于远程服务调用这个关键问题的历史、发展与现状,我还会在服务设计风格的“远程服务调用”部分(第7~10讲),以现代RPC和RESTful为主角,来进行更详细的讲述。而对于在原始分布式时代中遭遇到的其他问题,我也还会在软件架构演进的后面几个时代里,反复提起它们。

小结

今天,我给你介绍了计算机科学对分布式和服务化的第一次探索,着重分析了这次探索的主旨思想,也就是追求简单、符合Unix哲学的分布式系统,以及它当时所面临的困难,比如在捉襟见肘的运算能力、网络带宽下,设计不得不做出的妥协。

在这个过程中,我们接触到了DCE、CORBA等早期的分布式基础架构。其中许多的技术,比如远程服务调用、分布式文件系统、Kerberos认证协议等。如果你对这些技术觉得还有点陌生、或者还有很多疑惑,没有关系,我还会在后面的课程中为你着重介绍。

原始分布式时代提出的构建“符合Unix的设计哲学的”,以及“如同本地调用一般简单透明的”分布式系统的这个目标,是软件开发者对分布式系统最初的美好愿景。不过迫于现实,它会在一定时期内被妥协、被舍弃,分布式将会经过一段越来越复杂的发展进程。

但是,到了三十多年以后的今天,随着微服务的逐渐成熟完善,成为大型软件的主流架构风格以后,这个美好的愿景终将还是会重新被开发者拾起。

一课一思

Richard P. Gabriel提出的Unix设计哲学中写到:“保持接口与实现的简单性,比系统的任何其他属性,包括准确性、一致性和完整性,都来得更加重要。”

现在你来思考一下:今天以微服务为代表的分布式系统,是如何看待“简单”的?欢迎在留言区分享你的见解,我也将会在第5讲“后微服务时代”中,带你一起重新审视这个问题。

好,这节课就到这里。如果你身边也有想要或者必须要了解架构的演进的朋友,欢迎你把这一讲的内容分享给她/他。